#ifndef CONVERTER_H
#define CONVERTER_H
/*************************************************************/
// Filename: encoder.hpp
// Date: 2010-04-29
// Auther: Josh Wolfhead <wolfhead87@gmail.com>
// Purpose: define sdo classes of charset encoding and decoding
/*************************************************************/

#include <string.h>

#include <string>


#include "type.h"
#include "charset.hpp"


namespace sdo
{

	template<class charset_from, class charset_to>
	class CEncoder
	{
	private:
		typedef typename charset_from::traits::const_pointer_type input_type;
		typedef typename charset_to::traits::string_type string_type;
		typedef typename charset_to::traits::const_pointer_type const_pointer_type;
	public:
		explicit CEncoder(input_type src)
		{
			m_buf = impl::encodeIMPL<charset_from, charset_to>::encode(src);
		}
	public:
		typename const_pointer_type 
			operator () () const 
		{
			return m_buf.c_str();
		};

		operator typename const_pointer_type
			() const
		{
			return m_buf.c_str();
		}

		operator typename string_type
			() const
		{
			return m_buf;
		}

		_uint32_ size() const
		{
			return m_buf.size();
		}

	private:
		string_type m_buf;
	};



	namespace impl
	{
		template<class charset_from, class charset_to>
		class encodeIMPL{};

		template<>
		class encodeIMPL<unicode, utf8>
		{
		public:
			static
				utf8::traits::string_type
				encode(unicode::traits::const_pointer_type src)
			{
				utf8::traits::string_type outBuf;

				_uint32_ srcLen = wcslen(src);
				char _b[3] = {0};

				for (_uint32_ i = 0; i < srcLen; ++i)
				{
					_uint32_ len = UnicodeToUTF8(_b, *(src + i));
					for ( _uint32_ j = 0; j < len; ++j )
					{
						outBuf.append(1, _b[j]);
					}
				}
				return outBuf;
			}

		private:
			static int UnicodeToUTF8(char* putf8,wchar_t Unicode)
			{
				if (Unicode < 0x80)
				{
					putf8[0] = Unicode >> 0 & 0x7F | 0x00;
					return 1; 
				}
				else if (Unicode < 0x800)
				{
					putf8[0] = Unicode >>6 & 0x1F | 0xC0;
					putf8[1] = Unicode >>0 & 0x3F | 0x80;
					return 2;
				}
				else
				{
					putf8[0] = Unicode >> 12 & 0x0F | 0xE0;
					putf8[1] = Unicode >> 6 & 0x3F | 0x80;
					putf8[2] = Unicode >> 0 & 0x3F | 0x80;
					return 3;
				}
			}
		};


		template<>
		class encodeIMPL<utf8, unicode>
		{
		public:
			static
				unicode::traits::string_type
				encode(utf8::traits::const_pointer_type src)
			{
				unicode::traits::string_type outBuf;
				_uint32_ srcLen = strlen(src);
				wchar_t ch;

				for (_uint32_ i = 0; i < srcLen ;)
				{
					_uint32_ len = UTF8ToUnicode(&ch, src + i);
					outBuf.append(1, ch);
					i += len;
				}
				return outBuf;
			}

		private:
			static int UTF8ToUnicode(wchar_t* Unicode, const char* putf8)
			{
				*Unicode = 0;
				if ((putf8[0] & 0xE0) == 0xE0)
				{
					*Unicode |= (putf8[0] & 0x0F) << 12; 
					*Unicode |= (putf8[1] & 0x3F) << 6; 
					*Unicode |= (putf8[2] & 0x3F) << 0; 

					return 3;
				}
				else if ((putf8[0] & 0xC0) == 0xC0)
				{
					*Unicode |= (putf8[0] & 0x1F) << 6; 
					*Unicode |= (putf8[1] & 0x3F) << 0; 

					return 2;
				}
				else
				{
					*Unicode |= (putf8[0] & 0x7F) << 0; 
					return 1;
				}
			}
		};


		template<>
		class encodeIMPL<utf8, urlencode>
		{
		public:
			static
				urlencode::traits::string_type
				encode(utf8::traits::const_pointer_type src)
			{
				static    char hex[] = "0123456789ABCDEF";
				urlencode::traits::string_type dst;
				_uint32_ srcLen = strlen(src);

				for (_uint32_ i = 0; i < srcLen; i++)
				{
					unsigned char ch = src[i];
					if (isalnum(ch))
					{
						dst += ch;
					}
					else
						if (src[i] == ' ')
						{
							dst += '+';
						}
						else
						{
							_uint8_ c = static_cast<_uint8_>(src[i]);
							dst += '%';
							dst += hex[c / 16];
							dst += hex[c % 16];
						}
				}
				return dst;
			}
		};


		template<>
		class encodeIMPL<urlencode, utf8>
		{
		public:
			static
				utf8::traits::string_type
				encode(urlencode::traits::const_pointer_type src)
			{
				utf8::traits::string_type dst, dsturl;

				_uint32_ srcLen = strlen(src);

				for (_uint32_ i = 0; i < srcLen; i++)
				{
					if (src[i] == '%')
					{
						if(isxdigit(src[i + 1]) && isxdigit(src[i + 2]))
						{
							_uint8_ c1 = src[++i];
							_uint8_ c2 = src[++i];
							c1 = c1 - 48 - ((c1 >= 'A') ? 7 : 0) - ((c1 >= 'a') ? 32 : 0);
							c2 = c2 - 48 - ((c2 >= 'A') ? 7 : 0) - ((c2 >= 'a') ? 32 : 0);
							dst += (_uint8_)(c1 * 16 + c2);
						}
					}
					else
						if (src[i] == '+')
						{
							dst += ' ';
						}
						else
						{
							dst += src[i];
						}
				}

				return dst;
			}
		private:
			static _uint32_ utf8_decode( char *s, _uint32_ *pi )
			{
				_uint32_ c;
				int i = *pi;
				/* one digit utf-8 */
				if ((s[i] & 128)== 0 ) {
					c = (_uint32_) s[i];
					i += 1;
				} else if ((s[i] & 224)== 192 ) { /* 110xxxxx & 111xxxxx == 110xxxxx */
					c = (( (_uint32_) s[i] & 31 ) << 6) +
						( (_uint32_) s[i+1] & 63 );
					i += 2;
				} else if ((s[i] & 240)== 224 ) { /* 1110xxxx & 1111xxxx == 1110xxxx */
					c = ( ( (_uint32_) s[i] & 15 ) << 12 ) +
						( ( (_uint32_) s[i+1] & 63 ) << 6 ) +
						( (_uint32_) s[i+2] & 63 );
					i += 3;
				} else if ((s[i] & 248)== 240 ) { /* 11110xxx & 11111xxx == 11110xxx */
					c =  ( ( (_uint32_) s[i] & 7 ) << 18 ) +
						( ( (_uint32_) s[i+1] & 63 ) << 12 ) +
						( ( (_uint32_) s[i+2] & 63 ) << 6 ) +
						( (_uint32_) s[i+3] & 63 );
					i+= 4;
				} else if ((s[i] & 252)== 248 ) { /* 111110xx & 111111xx == 111110xx */
					c = ( ( (_uint32_) s[i] & 3 ) << 24 ) +
						( ( (_uint32_) s[i+1] & 63 ) << 18 ) +
						( ( (_uint32_) s[i+2] & 63 ) << 12 ) +
						( ( (_uint32_) s[i+3] & 63 ) << 6 ) +
						( (_uint32_) s[i+4] & 63 );
					i += 5;
				} else if ((s[i] & 254)== 252 ) { /* 1111110x & 1111111x == 1111110x */
					c = ( ( (_uint32_) s[i] & 1 ) << 30 ) +
						( ( (_uint32_) s[i+1] & 63 ) << 24 ) +
						( ( (_uint32_) s[i+2] & 63 ) << 18 ) +
						( ( (_uint32_) s[i+3] & 63 ) << 12 ) +
						( ( (_uint32_) s[i+4] & 63 ) << 6 ) +
						( (_uint32_) s[i+5] & 63 );
					i += 6;
				} else {
					c = '?';
					i++;
				}
				*pi = i;
				return c;
			}
		};
	}


};


#endif // CONVERTER_H